/**
 ******************************************************************************
 * @file    httpc.c
 * @author  Chavis.Chen
 * @version V1.0.0
 * @date    06-Dec-2018
 * @brief   This file defines the http client external APIs.
 ******************************************************************************
 *
 *  The MIT License
 *  Copyright (c) 2018 Chavis.Chen.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is furnished
 *  to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 *  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 ******************************************************************************
 */


#include "httpc.h"
#include "url_utils.h"
#include "string_utils.h"
#include "netdb.h"
#include "sockets.h"
#include "net.h"


static OSStatus httpc_errno_lock(httpc_t * httpc)
{
	OSStatus err = kNoErr;

	require_action(httpc && httpc->err_mgr.mutex, exit, err = kParamErr);

	err = rtos_mutex_lock(httpc->err_mgr.mutex, QUEC_WAIT_FOREVER);
	require_noerr(err, exit);
	
exit:
	return err;
}

static OSStatus httpc_errno_unlock(httpc_t * httpc)
{
	OSStatus err = kNoErr;

	require_action(httpc && httpc->err_mgr.mutex, exit, err = kParamErr);

	err = rtos_mutex_unlock(httpc->err_mgr.mutex);
	require_noerr(err, exit);
	
exit:
	return err;
}

static OSStatus httpc_wait_for_send_proc_task_ready(httpc_t * httpc)
{
	OSStatus err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	err = rtos_semaphore_wait(httpc->sock.send.process.rdy_sem, QUEC_WAIT_FOREVER);
	require_noerr(err, exit);

exit:
	return err;
}

static OSStatus httpc_wait_for_recv_proc_task_ready(httpc_t * httpc)
{
	OSStatus err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	err = rtos_semaphore_wait(httpc->sock.recv.preprocess.rdy_sem, QUEC_WAIT_FOREVER);
	require_noerr(err, exit);

	err = rtos_semaphore_wait(httpc->sock.recv.process.rdy_sem, QUEC_WAIT_FOREVER);
	require_noerr(err, exit);

exit:
	return err;
}

static OSStatus httpc_wait_for_proc_task_ready(httpc_t * httpc)
{
	OSStatus err = kNoErr;

	err = httpc_wait_for_send_proc_task_ready(httpc);
	require_noerr(err, exit);

	err = httpc_wait_for_recv_proc_task_ready(httpc);
	require_noerr(err, exit);

exit:
	return err;
}

static void httpc_send_process(void * argv)
{
	OSStatus 	err = kNoErr;
	httpc_t	*		httpc = (httpc_t *)argv;
	uint32			wm_cnt = 0;
	uint8 			send_buf[1460] = {0};
	uint32			to_send_length = 0;
	uint32			actually_read_len = 0;
	uint32			sent_len_last_time = 0;
	uint32			totally_send_len = 0;
	fd_set 			write_fds;
	int				ret = 0;

	require(httpc, exit);

	err = rtos_semaphore_release(httpc->sock.send.process.rdy_sem);
	require_noerr(err, exit);

	setsockopt(httpc->sock.fd, SOL_SOCKET, SO_SNDBUF, &httpc->sock.send.buf_sz, sizeof(httpc->sock.send.buf_sz));
	
	while(1)
	{
		
	bail:
		
		err = httpc_request_wm_lock(&httpc->request);
		require_noerr(err, exit);

		err = wm_cnt(&httpc->request.wm_mgr.handle, &wm_cnt);
		require_noerr(err, exit);

		err = httpc_request_wm_unlock(&httpc->request);
		require_noerr(err, exit);

		if(!wm_cnt && sent_len_last_time == to_send_length)
		{
			err = rtos_semaphore_wait(httpc->sock.send.process.sem, QUEC_WAIT_FOREVER);			
			require_noerr(err, exit);

			goto bail;
		}

		err = httpc_request_wm_lock(&httpc->request);
		require_noerr(err, exit);

		memcpy(send_buf, send_buf + sent_len_last_time, to_send_length - sent_len_last_time);

		to_send_length = (to_send_length - sent_len_last_time);

		err = wm_read(&httpc->request.wm_mgr.handle, send_buf + to_send_length, sizeof(send_buf) - to_send_length, &actually_read_len, httpc->request.wm_mgr.handle);
		require_noerr(err, exit);

		to_send_length += actually_read_len;
		
		err = httpc_request_wm_unlock(&httpc->request);
		require_noerr(err, exit);

		if(httpc->sock.ssl.enable == FALSE)
		{
			ret = send(httpc->sock.fd, send_buf, to_send_length, 0);
			sent_len_last_time = (ret > 0 ? ret : 0);

			if(ret > 0)
			{
				totally_send_len += ret;
			}
			else if(ret == 0)
			{
				httpc_set_errno(httpc, HTTP_ERR_SOCK_SEND_FAIL);
				
				if(httpc->sock.event_cb)
					httpc->sock.event_cb(httpc, HTTP_EVENT_SEND_FAIL, 0);
				
				while(1)
				{
					rtos_task_sleep_s(1);
				}
			}
			else
			{
				if(!(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN))
				{
					httpc_set_errno(httpc, HTTP_ERR_SOCK_SEND_FAIL);
					
					if(httpc->sock.event_cb)
						httpc->sock.event_cb(httpc, HTTP_EVENT_SEND_FAIL, 0);
					
					while(1)
					{
						rtos_task_sleep_s(1);
					}
				}
				else
				{
					rtos_task_sleep_ms(20);
				}
			}
		}
		else
		{
			int ssl_ret;

			require(httpc->sock.ssl.session_ptr, exit);
			
			ssl_ret = wolfSSL_write(httpc->sock.ssl.session_ptr, send_buf, to_send_length);
			
			httpc_log("ssl write ret: %d", ssl_ret);
			
			sent_len_last_time = (ssl_ret > 0 ? ssl_ret : 0);
			
			if(ssl_ret == WOLFSSL_FATAL_ERROR)
			{
				int ssl_error;

				ssl_error = wolfSSL_get_last_error(httpc->sock.ssl.session_ptr);
				switch(ssl_error)
				{
					case WANT_READ:
					case WANT_WRITE:
					{
						rtos_task_sleep_ms(20);
						break;
					}
					
					default:
					{
						httpc_set_errno(httpc, HTTP_ERR_SOCK_SEND_FAIL);
						
						if(httpc->sock.event_cb)
							httpc->sock.event_cb(httpc, HTTP_EVENT_SEND_FAIL, 0);
						
						while(1)
						{
							rtos_task_sleep_s(1);
						}
					}
				}
			}
			else if(ssl_ret < 0)
			{
				httpc_set_errno(httpc, HTTP_ERR_SOCK_SEND_FAIL);
				
				if(httpc->sock.event_cb)
					httpc->sock.event_cb(httpc, HTTP_EVENT_SEND_FAIL, 0);
				
				while(1)
				{
					rtos_task_sleep_s(1);
				}
			}
			else if(ssl_ret > 0)
			{
				totally_send_len += ssl_ret;
			}
			else if(ssl_ret == 0)
			{
				rtos_task_sleep_ms(20);
			}
		}
	}

exit:
	QUEC_ASSERT(0);
}

static OSStatus httpc_recv_retrieve_is_paused(httpc_t * httpc, bool * pause_status)
{
	OSStatus	err = kNoErr;

	require_action(httpc && pause_status, exit, err = kParamErr);

	* pause_status = httpc->sock.recv.ioctl.recv_retrieve_pause_enable;
	
exit:
	return err;
}

static OSStatus httpc_recv_wait_for_retrieve_continue(httpc_t * httpc)
{
	OSStatus	err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	err = rtos_task_suspend(httpc->sock.recv.process.task);
	require_noerr(err, exit);

exit:
	return err;
}

static OSStatus httpc_dwnlk_is_paused(httpc_t * httpc, bool * pause_status)
{
	OSStatus	err = kNoErr;

	require_action(httpc && pause_status, exit, err = kParamErr);

	* pause_status = httpc->sock.recv.ioctl.dwnlk_pause_enable;
	
exit:
	return err;
}

static OSStatus httpc_dwnlk_wait_for_continue(httpc_t * httpc)
{
	OSStatus	err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	if(httpc->sock.event_cb)
		httpc->sock.event_cb(httpc, HTTP_EVENT_DWNLK_PAUSE, 0);

	err = rtos_task_suspend(httpc->sock.recv.preprocess.task);
	require_noerr(err, exit);

exit:
	return err;
}

static void httpc_recv_preprocess(void * argv)
{
	OSStatus		err = kNoErr;
	httpc_t	*			httpc = (httpc_t *)argv;
	int 				ret = 0;
	fd_set 				read_fds;
	uint8 				recv_buf[1460] = {0};
	uint32				wm_cnt = 0;
	uint32				actually_write_len = 0;
	struct timeval		t;
	bool				pause_status = FALSE;
	
	require(httpc, exit);
	
	err = rtos_semaphore_release(httpc->sock.recv.preprocess.rdy_sem);
	require_noerr(err, exit);

	setsockopt(httpc->sock.fd, SOL_SOCKET, SO_RCVBUF, &httpc->sock.recv.buf_sz, sizeof(httpc->sock.recv.buf_sz));

	while(1)
	{
		err = httpc_dwnlk_is_paused(httpc, &pause_status);
		require_noerr(err, exit);

		if(pause_status == TRUE)
		{
			err = httpc_dwnlk_wait_for_continue(httpc);
			require_noerr(err, exit);
		}

		t.tv_sec = 1;
		t.tv_usec = 0;

		FD_ZERO(&read_fds);
		FD_SET(httpc->sock.fd, &read_fds);
		
		ret = select(httpc->sock.fd + 1, &read_fds, NULL, NULL, &t);

		if(ret > 0)
		{
			if(FD_ISSET(httpc->sock.fd, &read_fds))
			{
				if(httpc->sock.ssl.enable == FALSE)
				{
					ret = recv(httpc->sock.fd, recv_buf, sizeof(recv_buf), 0);
					if(ret > 0)
					{
						err = httpc_response_wm_lock(&httpc->response);
						require_noerr(err, exit);

						err = wm_cnt(&httpc->response.wm_mgr.handle, &wm_cnt);
						require_noerr(err, exit);

						err = wm_write(&httpc->response.wm_mgr.handle, recv_buf, ret, &actually_write_len, httpc->response.wm_mgr.handle);
						require_noerr(err, exit);

						if(wm_cnt == 0 && actually_write_len != 0)
						{
							err = rtos_semaphore_release(httpc->sock.recv.process.sem);
							require_noerr(err, exit);
						}

						err = httpc_response_wm_unlock(&httpc->response);
						require_noerr(err, exit);
					}
					else if(ret == 0)
					{
						httpc_set_errno(httpc, HTTP_ERR_SOCK_RECV_FAIL);
						
						if(httpc->sock.event_cb)
							httpc->sock.event_cb(httpc, HTTP_EVENT_DWNLK_FAIL, 0);
						
						while(1)
						{
							rtos_task_sleep_s(1);
						}
					}
					else
					{
						if(!(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN))
						{
							httpc_set_errno(httpc, HTTP_ERR_SOCK_RECV_FAIL);
							
							if(httpc->sock.event_cb)
								httpc->sock.event_cb(httpc, HTTP_EVENT_DWNLK_FAIL, 0);
							
							while(1)
							{
								rtos_task_sleep_s(1);
							}
						}
						else
						{
							rtos_task_sleep_ms(20);
						}
					}
				}
				else
				{
					int ssl_ret;

					require(httpc->sock.ssl.session_ptr, exit);

					ssl_ret = wolfSSL_read(httpc->sock.ssl.session_ptr, recv_buf, sizeof(recv_buf));
					httpc_log("ssl read ret: %d", ssl_ret);

					if(ssl_ret == WOLFSSL_FATAL_ERROR)
					{
						int ssl_error;

						ssl_error = wolfSSL_get_last_error(httpc->sock.ssl.session_ptr);
						switch(ssl_error)
						{
							case WANT_READ:
							case WANT_WRITE:
							{
								rtos_task_sleep_ms(20);
								break;
							}
							
							default:
							{
								httpc_set_errno(httpc, HTTP_ERR_SOCK_RECV_FAIL);
								
								if(httpc->sock.event_cb)
									httpc->sock.event_cb(httpc, HTTP_EVENT_DWNLK_FAIL, 0);
								
								while(1)
								{
									rtos_task_sleep_s(1);
								}
							}
						}
					}
					else if(ssl_ret < 0)
					{
						httpc_set_errno(httpc, HTTP_ERR_SOCK_RECV_FAIL);
						
						if(httpc->sock.event_cb)
							httpc->sock.event_cb(httpc, HTTP_EVENT_DWNLK_FAIL, 0);
						
						while(1)
						{
							rtos_task_sleep_s(1);
						}
					}
					else if(ssl_ret > 0)
					{
						err = httpc_response_wm_lock(&httpc->response);
						require_noerr(err, exit);

						err = wm_cnt(&httpc->response.wm_mgr.handle, &wm_cnt);
						require_noerr(err, exit);

						err = wm_write(&httpc->response.wm_mgr.handle, recv_buf, ssl_ret, &actually_write_len, httpc->response.wm_mgr.handle);
						require_noerr(err, exit);

						if(wm_cnt == 0 && actually_write_len != 0)
						{
							err = rtos_semaphore_release(httpc->sock.recv.process.sem);
							require_noerr(err, exit);
						}

						err = httpc_response_wm_unlock(&httpc->response);
						require_noerr(err, exit);
					}
					else if(ssl_ret == 0)
					{
						rtos_task_sleep_ms(20);
					}
				}
			}
		}
	}
	
exit:
	QUEC_ASSERT(0);
}

static void httpc_recv_process(void * argv)
{
	OSStatus			err = kNoErr;
	httpc_t *				httpc = (httpc_t *)argv;
	uint32					wm_cnt = 0;
	bool					pause_status = FALSE;
	bool					is_recvd_head = FALSE;

	require(httpc, exit);

	err = rtos_semaphore_release(httpc->sock.recv.process.rdy_sem);
	require_noerr(err, exit);

	while(1)
	{
		err = httpc_recv_retrieve_is_paused(httpc, &pause_status);
		require_noerr(err, exit);

		if(pause_status == TRUE)
		{
			err = httpc_recv_wait_for_retrieve_continue(httpc);
			require_noerr(err, exit);
		}

		err = httpc_response_wm_lock(&httpc->response);
		require_noerr(err, exit);

		err = wm_cnt(&httpc->response.wm_mgr.handle, &wm_cnt);
		require_noerr(err, exit);
		
		err = httpc_response_wm_unlock(&httpc->response);
		require_noerr(err, exit);

		if(!wm_cnt)
		{
			err = rtos_semaphore_wait(httpc->sock.recv.process.sem, QUEC_WAIT_FOREVER);
			require_noerr(err, exit);
		}

		if(is_recvd_head == FALSE)
		{
			err = httpc_response_head_parse(&httpc->response, httpc->sock.recv.timeout_ms, !httpc->sock.recv.user_require_head);
			
			switch(err)
			{
				case kNoErr:
					if(httpc->sock.event_cb)
					{
						httpc->sock.event_cb(httpc, HTTP_EVENT_DATA_INCOME, HTTP_RESP_TYPE_HEAD);
						if(httpc->sock.recv.user_require_head)
						{
							httpc->sock.event_cb(httpc, HTTP_EVENT_DATA_INCOME, HTTP_RESP_TYPE_WHOLE);
						}
					}
					break;
					
				case kVersionErr:
					httpc_set_errno(httpc, HTTP_ERR_RESP_INVALID_VER);
					if(httpc->sock.event_cb)
						httpc->sock.event_cb(httpc, HTTP_EVENT_DATA_INCOME, 0);
					while(1)
					{
						rtos_task_sleep_s(1);
					}
					break;

				case kFormatErr:
					httpc_set_errno(httpc, HTTP_ERR_RESP_INVALID_FORMAT);
					if(httpc->sock.event_cb)
						httpc->sock.event_cb(httpc, HTTP_EVENT_DATA_INCOME, 0);
					while(1)
					{
						rtos_task_sleep_s(1);
					}
					break;

				case kNoMemoryErr:
					httpc_set_errno(httpc, HTTP_ERR_NO_MEMORY);
					if(httpc->sock.event_cb)
						httpc->sock.event_cb(httpc, HTTP_EVENT_DATA_INCOME, 0);
					while(1)
					{
						rtos_task_sleep_s(1);
					}
					break;

				default:
					require_noerr(err, exit);
					break;
			}
			is_recvd_head = TRUE;
		}
		else
		{
			if(httpc->sock.event_cb)
				httpc->sock.event_cb(httpc, HTTP_EVENT_DATA_INCOME, httpc->sock.recv.user_require_head ? HTTP_RESP_TYPE_WHOLE : HTTP_RESP_TYPE_BODY);
		}
	}
	
exit:
	QUEC_ASSERT(0);
}

static OSStatus httpc_send_proc_stop(httpc_t * httpc)
{
	OSStatus 	err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	if(!httpc->sock.send.is_proc_started)
		return kNoErr;

	err = rtos_task_delete_ex(httpc->sock.send.process.task);
	require_noerr(err, exit);

	err = rtos_semaphore_delete(httpc->sock.send.process.rdy_sem);
	require_noerr(err, exit);

	err = rtos_semaphore_delete(httpc->sock.send.process.sem);
	require_noerr(err, exit);

	httpc->sock.send.is_proc_started = FALSE;

exit:
	return err;
}

static OSStatus httpc_recv_proc_stop(httpc_t * httpc)
{
	OSStatus 	err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	if(!httpc->sock.recv.is_proc_started)
	{
		return kNoErr;
	}

	err = rtos_task_delete_ex(httpc->sock.recv.preprocess.task);
	require_noerr(err, exit);

	err = rtos_task_delete_ex(httpc->sock.recv.process.task);
	require_noerr(err, exit);

	err = rtos_semaphore_delete(httpc->sock.recv.preprocess.rdy_sem);
	require_noerr(err, exit);

	err = rtos_semaphore_delete(httpc->sock.recv.preprocess.sem);
	require_noerr(err, exit);

	err = rtos_semaphore_delete(httpc->sock.recv.process.rdy_sem);
	require_noerr(err, exit);

	err = rtos_semaphore_delete(httpc->sock.recv.process.sem);
	require_noerr(err, exit);

	httpc->sock.recv.is_proc_started = FALSE;

exit:
	return err;
}

static OSStatus httpc_proc_stop(httpc_t * httpc)
{
	OSStatus err = kNoErr;

	require_action(httpc, exit, err = kParamErr);
	
	httpc_send_proc_stop(httpc);
	
	httpc_recv_proc_stop(httpc);

exit:
	return err;
}

OSStatus httpc_send_proc_start(httpc_t * httpc)
{
	OSStatus 	err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	err = rtos_semaphore_create(&httpc->sock.send.process.rdy_sem, 0);
	require_noerr(err, exit);

	err = rtos_semaphore_create(&httpc->sock.send.process.sem, 0);
	require_noerr(err, exit);
	
	err = rtos_task_create(&httpc->sock.send.process.task, 8*1024, QUEC_APP_TASK_PRIORITY, task_name[QUEC_TASK_NAME_HTTP_SEND], httpc_send_process, httpc);
	require_noerr(err, exit);

	err = httpc_wait_for_send_proc_task_ready(httpc);
	require_noerr(err, exit);

	httpc->sock.send.is_proc_started = TRUE;

exit:
	return err;
}

OSStatus httpc_recv_proc_start(httpc_t * httpc)
{
	OSStatus 	err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	err = rtos_semaphore_create(&httpc->sock.recv.preprocess.rdy_sem, 0);
	require_noerr(err, exit);

	err = rtos_semaphore_create(&httpc->sock.recv.preprocess.sem, 0);
	require_noerr(err, exit);

	err = rtos_task_create(&httpc->sock.recv.preprocess.task, 8*1024, QUEC_APP_TASK_PRIORITY, task_name[QUEC_TASK_NAME_HTTP_RECV_PRE], httpc_recv_preprocess, httpc);
	require_noerr(err, exit);

	err = rtos_semaphore_create(&httpc->sock.recv.process.rdy_sem, 0);
	require_noerr(err, exit);

	err = rtos_semaphore_create(&httpc->sock.recv.process.sem, 0);
	require_noerr(err, exit);
	
	err = rtos_task_create(&httpc->sock.recv.process.task, 8*1024, QUEC_APP_TASK_PRIORITY, task_name[QUEC_TASK_NAME_HTTP_RECV], httpc_recv_process, httpc);
	require_noerr(err, exit);

	err = httpc_wait_for_recv_proc_task_ready(httpc);
	require_noerr(err, exit);

	httpc->sock.recv.is_proc_started = TRUE;

exit:
	return err;
}

static OSStatus httpc_err_mgr_init(httpc_t * httpc)
{
	OSStatus err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	err = rtos_mutex_create(&httpc->err_mgr.mutex);
	require_noerr(err, exit);

	httpc->err_mgr.httpc_errno = HTTP_ERR_SUCCESS;

exit:
	return err;
}

OSStatus httpc_new(httpc_t * httpc)
{
	OSStatus err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	require_action(httpc->is_initialized == FALSE, exit, err = kAlreadyInitializedErr);

	err = httpc_request_new(&httpc->request, httpc->sock.send.buf_sz, httpc->sock.send.buf_sz * 0.2, httpc->sock.send.buf_sz * 0.8);
	require_noerr(err, exit);

	err = httpc_response_new(&httpc->response, httpc->sock.recv.buf_sz, httpc->sock.recv.buf_sz * 0.2, httpc->sock.recv.buf_sz * 0.8);
	require_noerr(err, exit);

	err = httpc_err_mgr_init(httpc);
	require_noerr(err, exit);

	httpc->is_initialized = TRUE;
	
exit:
	if(err != kNoErr && httpc != NULL && httpc->is_initialized == FALSE)
	{
		httpc_request_destroy(&httpc->request);
		httpc_response_destroy(&httpc->response);

		if(httpc->err_mgr.mutex != NULL)
		{
			rtos_mutex_delete(httpc->err_mgr.mutex);
			httpc->err_mgr.mutex = NULL;
		}

		memset(httpc, 0, sizeof(httpc_t));
		httpc->sock.fd = -1;
	}

	return err;
}

static void httpc_destroy_worker(void * argv)
{
	OSStatus	err = kNoErr;
	httpc_t * 		httpc = (httpc_t *)argv;
	int				sock_nbio	= 0;
	struct linger	linger = {0};

	err = httpc_proc_stop(httpc);
	require_noerr(err, exit);

	err = httpc_request_destroy(&httpc->request);
	require_noerr(err, exit);

	err = httpc_response_destroy(&httpc->response);
	require_noerr(err, exit);

	httpc_set_errno(httpc, HTTP_ERR_SUCCESS);

	if(httpc->err_mgr.mutex != NULL)
	{
		rtos_mutex_delete(httpc->err_mgr.mutex);
		httpc->err_mgr.mutex = NULL;
	}

	if(httpc->sock.fd >= 0)
	{
		if(httpc->sock.ssl.enable)
		{
			httpc_ssl_shutdown(httpc);
			httpc_ssl_free(httpc);
			httpc_ssl_ctx_deinit(httpc);
		}

		ioctlsocket(httpc->sock.fd, FIONBIO, &sock_nbio);

		linger.l_onoff = 1;
		linger.l_linger = httpc->sock.close.linger_time_s;

		setsockopt(httpc->sock.fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));

		close(httpc->sock.fd);

		httpc->sock.fd = -1;
	}

	if(httpc->sock.peer.domain)
	{
		free(httpc->sock.peer.domain);
		httpc->sock.peer.domain = NULL;
	}

	if(httpc->sock.peer.path)
	{
		free(httpc->sock.peer.path);
		httpc->sock.peer.path = NULL;
	}

	httpc->sock.recv.ioctl.dwnlk_pause_enable = FALSE;
	httpc->sock.recv.ioctl.recv_retrieve_pause_enable = FALSE;

	httpc->is_initialized = FALSE;

exit:

	rtos_task_delete_ex(NULL);

}

OSStatus httpc_destroy(httpc_t * httpc)
{
	OSStatus err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	if(httpc->is_initialized == FALSE)
		return kNoErr;

	err = rtos_task_create(&httpc->destroy.task, 4*1024, QUEC_APP_TASK_PRIORITY, task_name[QUEC_TASK_NAME_HTTP_DESTROY], httpc_destroy_worker, httpc);
	require_noerr(err, exit);

exit:

	return err;
}

void httpc_set_errno(httpc_t * httpc, httpc_errno_e httpc_errno)
{
	if(httpc)
	{
		if(!httpc->is_initialized)
			return;
		
		httpc_errno_lock(httpc);
		
		httpc->err_mgr.httpc_errno = httpc_errno;

		httpc_errno_unlock(httpc);
	}
}

OSStatus httpc_get_errno(httpc_t * httpc, httpc_errno_e * httpc_errno)
{
	OSStatus err = kNoErr;

	require_action(httpc && httpc_errno, exit, err = kParamErr);

	require_action(httpc->is_initialized == TRUE, exit, err = kNotInitializedErr);

	httpc_errno_lock(httpc);

	* httpc_errno = httpc->err_mgr.httpc_errno;

	httpc_errno_unlock(httpc);
	
exit:
	return err;
}

OSStatus httpc_url_parse(httpc_t * httpc)
{
	OSStatus 	err 	 	= kNoErr;
	URLComponents 	outComponents = {0};
	char *			url 		= NULL;			// malloc
    char *			scheme   	= NULL;			// malloc
    char *			host     	= NULL;			// malloc
    char *			path     	= NULL;			// malloc
    char *			query     	= NULL;			// malloc
	int				port	 	= 0;
	char *			chr_colon	= NULL;

	require_action(httpc, exit, err = kParamErr);

	if(httpc->sock.url.ptr == NULL || httpc->sock.url.len == 0)
	{
		httpc_set_errno(httpc, HTTP_ERR_INVALID_URL);
		require_noerr(err = kGeneralErr, exit);
	}

	url = data2CString(httpc->sock.url.ptr, httpc->sock.url.len);
	require_action(url, exit, err = kNoMemoryErr);

	URLParseComponents(url, NULL, &outComponents, NULL);
	PrintURL(&outComponents);

	if(outComponents.schemePtr && outComponents.schemeLen)		// scheme
	{
		scheme = data2CString(outComponents.schemePtr, outComponents.schemeLen);
		require_action(scheme, exit, err = kNoMemoryErr);

		if(strcasecmp(scheme, "http") != 0 && strcasecmp(scheme, "https") != 0)
		{
			httpc_set_errno(httpc, HTTP_ERR_INVALID_URL);
			require_noerr(err = kGeneralErr, exit);
		}
	}
	else
	{
		scheme = malloc(5);
		require_action(scheme, exit, err = kNoMemoryErr);

		memcpy(scheme, "http", 5);
	}

	if(strcasecmp(scheme, "http") == 0)
		httpc->sock.ssl.enable = FALSE;
	else
		httpc->sock.ssl.enable = TRUE;

	if(outComponents.hostPtr && outComponents.hostLen)		// host
	{
		host = data2CString(outComponents.hostPtr, outComponents.hostLen);
		require_action(host, exit, err = kNoMemoryErr);
	}
	else
	{
		httpc_set_errno(httpc, HTTP_ERR_INVALID_URL);
		require_noerr(err = kGeneralErr, exit);
	}

	if(outComponents.pathPtr && outComponents.pathLen)		// path
	{
		path = data2CString(outComponents.pathPtr, outComponents.pathLen);
		require_action(path, exit, err = kNoMemoryErr);
	}
	else
	{
		path = malloc(2);
		require_action(path, exit, err = kNoMemoryErr);

		memcpy(path, "/", 2);
	}

	if(outComponents.queryPtr && outComponents.queryLen)		// query
	{
		query = data2CString(outComponents.queryPtr, outComponents.queryLen);
		require_action(query, exit, err = kNoMemoryErr);
	}

	chr_colon = strchr(host, CHR_COLON);
	if(chr_colon)
	{
		if(*(chr_colon - 1) == CHR_SPACE || *(chr_colon + 1) == CHR_SPACE)
		{
			httpc_set_errno(httpc, HTTP_ERR_INVALID_URL);
			require_noerr(err = kGeneralErr, exit);
		}
		
		* chr_colon = '\0';
		
		if(strlen(chr_colon + 1) != 0)
		{
			if(str2int32(chr_colon + 1, 10, &port) == TRUE)
			{
				if(!(port >= 0 && port <= 65535))
				{
					httpc_set_errno(httpc, HTTP_ERR_INVALID_URL);
					require_noerr(err = kGeneralErr, exit);
				}
			}
			else
			{
				httpc_set_errno(httpc, HTTP_ERR_INVALID_URL);
				require_noerr(err = kGeneralErr, exit);
			}
		}
		else
		{
			if(httpc->sock.ssl.enable == TRUE)
				port = 443;
			else
				port = 80;
		}
	}
	else
	{
		if(host[strlen(host) - 1] == CHR_SPACE)
		{
			httpc_set_errno(httpc, HTTP_ERR_INVALID_URL);
			require_noerr(err = kGeneralErr, exit);
		}
		else
		{
			if(httpc->sock.ssl.enable == TRUE)
				port = 443;
			else
				port = 80;
		}
	}

	if(!(port >= 0 && port <= 65535))
	{
		httpc_set_errno(httpc, HTTP_ERR_INVALID_URL);
		require_noerr(err = kGeneralErr, exit);
	}
	
	httpc->sock.peer.domain = malloc(strlen(host)+1);
	require_action(httpc->sock.peer.domain, exit, err = kNoMemoryErr);
	memcpy(httpc->sock.peer.domain, host, strlen(host)+1);

	httpc->sock.peer.path = malloc(query == NULL ? strlen(path)+1 : strlen(path)+strlen(query)+2);
	require_action(httpc->sock.peer.path, exit, err = kNoMemoryErr);
	memset(httpc->sock.peer.path, 0, query == NULL ? strlen(path)+1 : strlen(path)+strlen(query)+2);
	if(query)
		sprintf(httpc->sock.peer.path, "%s?%s", path, query);
	else
		sprintf(httpc->sock.peer.path, "%s", path);

	httpc->sock.peer.port = port;

	httpc_log("http remote host: %s", httpc->sock.peer.domain);
	httpc_log("http remote port: %d", httpc->sock.peer.port);
	httpc_log("http remote path: %s", httpc->sock.peer.path);
	
exit:

	if(url)			free(url);
	if(scheme)		free(scheme);
	if(host)		free(host);
	if(path)		free(path);
	if(query)		free(query);

	if(err != kNoErr)
	{
		if(err == kParamErr)
		{
			httpc_set_errno(httpc, HTTP_ERR_INVALID_OBJ);
		}
		if(err == kNoMemoryErr)
		{
			httpc_set_errno(httpc, HTTP_ERR_NO_MEMORY);
		}
	}

	return err;
}

static int  httpc_ssl_pem_password_cb(char* pwd, int pwd_len, int mode, void * cb_params)
{
	uint16  				ssl_ctx_id = (uint16)cb_params;
	quectel_ssl_ctx *		qssl_ctx_ptr = NULL;
    int  					pwd_sz;

    qssl_ctx_ptr = quectel_ssl_get_ctx_ptr(ssl_ctx_id);

    pwd_sz = strlen((char *)(qssl_ctx_ptr->ssl_ctx_opt.ssl_client_key_pwd));

	pwd_sz = apptcpip_min_val(pwd_sz, pwd_len);

	if(pwd_sz > 0)
	{
		memcpy(pwd, qssl_ctx_ptr->ssl_ctx_opt.ssl_client_key_pwd, pwd_sz);
	}
	
	return pwd_sz;
}

static OSStatus httpc_ssl_ctx_init(httpc_t * httpc)
{
	OSStatus 			err = kNoErr;
	quectel_ssl_ctx * 		qssl_ctx_ptr = NULL;
	WOLFSSL_CTX *			ssl_ctx_ptr = NULL;
	quectel_ssl_ctx_opt *	ssl_ctx_opt = NULL;

	require_action(httpc, exit, err = kParamErr);

	require_action(httpc->sock.ssl.enable, exit, err = kConfigErr);

	qssl_ctx_ptr = quectel_ssl_get_ctx_ptr(httpc->sock.ssl.ctx_id);

	ssl_ctx_opt = &(qssl_ctx_ptr->ssl_ctx_opt);

	if(qssl_ctx_ptr->ssl_ctx_ptr == NULL)
	{
		require_action(ssl_ctx_opt->ssl_version == QUEC_SSL_VER_30 || 
					   ssl_ctx_opt->ssl_version == QUEC_SSL_VER_TLS10 || 
					   ssl_ctx_opt->ssl_version == QUEC_SSL_VER_TLS11 || 
					   ssl_ctx_opt->ssl_version == QUEC_SSL_VER_TLS12 || 
					   ssl_ctx_opt->ssl_version == QUEC_SSL_VER_ALL,
					  exit, err = kConfigErr);

		require_action(!((ssl_ctx_opt->ssl_version == QUEC_SSL_VER_TLS10 && ssl_ctx_opt->ssl_ca_cert_path == NULL) ||
		   				  (ssl_ctx_opt->ssl_version == QUEC_SSL_VER_TLS11 && (ssl_ctx_opt->ssl_ca_cert_path == NULL ||ssl_ctx_opt->ssl_client_cert_path == NULL || ssl_ctx_opt->ssl_client_key_path== NULL))), 
		   				  exit, err = kConfigErr);

		switch(ssl_ctx_opt->ssl_version)
		{
			case QUEC_SSL_VER_30:
				ssl_ctx_ptr = wolfSSL_CTX_new(wolfSSLv3_client_method());
				break;
			case QUEC_SSL_VER_TLS10:
				ssl_ctx_ptr = wolfSSL_CTX_new(wolfTLSv1_client_method());
				break;
			case QUEC_SSL_VER_TLS11:
				ssl_ctx_ptr = wolfSSL_CTX_new(wolfTLSv1_1_client_method());
				break;
			case QUEC_SSL_VER_TLS12:
				ssl_ctx_ptr = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
				break;
			case QUEC_SSL_VER_ALL:
				ssl_ctx_ptr = wolfSSL_CTX_new(wolfSSLv23_client_method());
				break;
		}

		require_action(ssl_ctx_ptr, exit, err = kGeneralErr);
		
		if(ssl_ctx_opt->ssl_cs_list.size > 0)
		{
			wolfSSL_CTX_set_ciphersuite_list(ssl_ctx_ptr, ssl_ctx_opt->ssl_cs_list.list, ssl_ctx_opt->ssl_cs_list.size);
		}

		wolfSSL_CTX_set_ignore_expiration(ssl_ctx_ptr, ssl_ctx_opt->ssl_ignore_expiration);
		wolfSSL_CTX_set_ignore_multicertchain_verify(ssl_ctx_ptr, ssl_ctx_opt->ssl_ignore_multicertchain_verify);
		wolfSSL_CTX_set_ignore_invlaidcertsign(ssl_ctx_ptr, ssl_ctx_opt->ssl_ignore_invalidcertsign);

		if(ssl_ctx_opt->ssl_verify_mode == QUEC_SSL_VERIFY_CLIENT_SERVER)
		{
			wolfSSL_CTX_set_default_passwd_cb_userdata(ssl_ctx_ptr, (void *)httpc->sock.ssl.ctx_id);
			wolfSSL_CTX_set_default_passwd_cb(ssl_ctx_ptr, httpc_ssl_pem_password_cb);
		}

		switch(ssl_ctx_opt->ssl_verify_mode)
		{
			case QUEC_SSL_VERIFY_NULL:
				wolfSSL_CTX_set_verify(ssl_ctx_ptr, SSL_VERIFY_NONE, NULL);
				break;
			case QUEC_SSL_VERIFY_CLIENT:
				wolfSSL_CTX_set_verify(ssl_ctx_ptr, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
				if(ssl_ctx_opt->ssl_ca_cert_path[0] == '\0' ||
				    wolfSSL_CTX_load_verify_locations(ssl_ctx_ptr, (char *)ssl_ctx_opt->ssl_ca_cert_path, NULL) != SSL_SUCCESS)
				{
					require_action(0, exit, err = kConfigErr);		// !!! force to exit for the ca cert path error !!!
				}
				break;
			case QUEC_SSL_VERIFY_CLIENT_SERVER:
				wolfSSL_CTX_set_verify(ssl_ctx_ptr, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
				if(ssl_ctx_opt->ssl_ca_cert_path[0] == '\0' ||
					wolfSSL_CTX_load_verify_locations(ssl_ctx_ptr, (char *)ssl_ctx_opt->ssl_ca_cert_path, NULL) != SSL_SUCCESS)
				{
					require_action(0, exit, err = kConfigErr);		// !!! force to exit for the ca cert path error !!!
				}
				if(ssl_ctx_opt->ssl_client_cert_path[0] == '\0' ||
					wolfSSL_CTX_use_certificate_chain_file(ssl_ctx_ptr, (char *)ssl_ctx_opt->ssl_client_cert_path) != SSL_SUCCESS)
				{
					require_action(0, exit, err = kConfigErr);		// !!! force to exit for the client cert path error !!!
				}
				if(ssl_ctx_opt->ssl_client_key_path[0] == '\0' ||
					wolfSSL_CTX_use_PrivateKey_file(ssl_ctx_ptr, (char *)ssl_ctx_opt->ssl_client_key_path, SSL_FILETYPE_PEM) != SSL_SUCCESS)
				{
					require_action(0, exit, err = kConfigErr);		// !!! force to exit for the client key path error !!!
				}
				break;
		}
		
		qssl_ctx_ptr->ssl_ctx_ptr = ssl_ctx_ptr;
	}

exit:
	if(err != kNoErr)
	{
		if(ssl_ctx_ptr)
			wolfSSL_CTX_free(ssl_ctx_ptr);
	}
	return err;
}

static OSStatus httpc_ssl_ctx_deinit(httpc_t * httpc)
{
	OSStatus 			err = kNoErr;
	quectel_ssl_ctx * 		qssl_ctx_ptr = NULL;

	require_action(httpc, exit, err = kParamErr);

	require_action(httpc->sock.ssl.enable, exit, err = kConfigErr);

	qssl_ctx_ptr = quectel_ssl_get_ctx_ptr(httpc->sock.ssl.ctx_id);

	require_action(qssl_ctx_ptr->ssl_ctx_ptr, exit, err = kGeneralErr);

	if(wolfSSL_CTX_get_ref_cnt(qssl_ctx_ptr->ssl_ctx_ptr) == 1)
	{
        wolfSSL_CTX_free(qssl_ctx_ptr->ssl_ctx_ptr);
        qssl_ctx_ptr->ssl_ctx_ptr = NULL;
	}

exit:
	return err;
}

static OSStatus httpc_ssl_new(httpc_t * httpc)
{
	OSStatus		err = kNoErr;
	quectel_ssl_ctx *	qssl_ctx_ptr = NULL;
    WOLFSSL *			ssl_session_ptr = NULL;

	require_action(httpc, exit, err = kParamErr);

	require_action(httpc->sock.ssl.enable, exit, err = kConfigErr);

	qssl_ctx_ptr = quectel_ssl_get_ctx_ptr(httpc->sock.ssl.ctx_id);
	require_action(qssl_ctx_ptr->ssl_ctx_ptr, exit, err = kGeneralErr);

	ssl_session_ptr = wolfSSL_new(qssl_ctx_ptr->ssl_ctx_ptr);
	require_action(ssl_session_ptr, exit, err = kGeneralErr);

	httpc->sock.ssl.session_ptr = ssl_session_ptr;

	tcpip_ssl_set_opening_ctx_id(httpc->sock.ssl.ctx_id);
	
exit:
	return err;
}

static OSStatus httpc_ssl_free(httpc_t * httpc)
{
	OSStatus		err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	require_action(httpc->sock.ssl.enable, exit, err = kConfigErr);

	require_action(httpc->sock.ssl.session_ptr, exit, err = kGeneralErr);

	wolfSSL_free(httpc->sock.ssl.session_ptr);

	httpc->sock.ssl.session_ptr = NULL;

exit:
	return err;
}

static OSStatus httpc_ssl_set_fd(httpc_t * httpc)
{
	OSStatus		err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	require_action(httpc->sock.ssl.enable, exit, err = kConfigErr);

	require_action(httpc->sock.ssl.session_ptr, exit, err = kGeneralErr);

	require_action(wolfSSL_set_fd(httpc->sock.ssl.session_ptr, httpc->sock.fd) == WOLFSSL_SUCCESS, exit, err = kGeneralErr);
	
	wolfSSL_set_using_nonblock(httpc->sock.ssl.session_ptr, 1);

exit:
	return err;
}

static OSStatus httpc_ssl_set_session(httpc_t * httpc)
{
	OSStatus		err = kNoErr;
	struct sockaddr		sockaddr = {0};
	socklen_t 			addrlen;

	require_action(httpc, exit, err = kParamErr);

	require_action(httpc->sock.ssl.enable, exit, err = kConfigErr);
	
	addrlen = sizeof(sockaddr);
	
	require_action(getpeername(httpc->sock.fd, &sockaddr, &addrlen) == 0, exit, err = kGeneralErr);

	if(httpc->sock.ai_family == AF_INET)
	{
		struct sockaddr_in * ip4addr = (struct sockaddr_in *)&sockaddr;

		if(ip4addr->sin_addr.s_addr == ((struct sockaddr_in *)&httpc->sock.ssl.session_reuse.sockaddr)->sin_addr.s_addr &&
			ip4addr->sin_port == ((struct sockaddr_in *)&httpc->sock.ssl.session_reuse.sockaddr)->sin_port)
		{
			require_action(httpc->sock.ssl.session_ptr, exit, err = kGeneralErr);
			require_action(wolfSSL_set_session(httpc->sock.ssl.session_ptr, httpc->sock.ssl.session_reuse.session_ptr) == WOLFSSL_SUCCESS, exit, err = kGeneralErr);
		}
	}
	else
	{
		struct sockaddr_in6 * ip6addr = (struct sockaddr_in6 *)&sockaddr;

		if(ip6addr->sin6_addr.s6_addr32[0] == ((struct sockaddr_in6 *)&httpc->sock.ssl.session_reuse.sockaddr)->sin6_addr.s6_addr32[0] &&
			ip6addr->sin6_addr.s6_addr32[1] == ((struct sockaddr_in6 *)&httpc->sock.ssl.session_reuse.sockaddr)->sin6_addr.s6_addr32[1] &&
			ip6addr->sin6_addr.s6_addr32[2] == ((struct sockaddr_in6 *)&httpc->sock.ssl.session_reuse.sockaddr)->sin6_addr.s6_addr32[2] &&
			ip6addr->sin6_addr.s6_addr32[3] == ((struct sockaddr_in6 *)&httpc->sock.ssl.session_reuse.sockaddr)->sin6_addr.s6_addr32[3] &&
			ip6addr->sin6_port == ((struct sockaddr_in6 *)&httpc->sock.ssl.session_reuse.sockaddr)->sin6_port)
		{
			require_action(httpc->sock.ssl.session_ptr, exit, err = kGeneralErr);
			require_action(wolfSSL_set_session(httpc->sock.ssl.session_ptr, httpc->sock.ssl.session_reuse.session_ptr) == WOLFSSL_SUCCESS, exit, err = kGeneralErr);
		}
	}

exit:
	return err;
}

static OSStatus httpc_ssl_use_sni(httpc_t * httpc)
{
	OSStatus			err = kNoErr;
	quectel_ssl_ctx *		qssl_ctx_ptr = NULL;

	require_action(httpc, exit, err = kParamErr);

	require_action(httpc->sock.ssl.enable, exit, err = kConfigErr);

	qssl_ctx_ptr = quectel_ssl_get_ctx_ptr(httpc->sock.ssl.ctx_id);

	if(qssl_ctx_ptr->ssl_ctx_opt.ssl_sni_ext)
	{
		require_action(httpc->sock.ssl.session_ptr, exit, err = kGeneralErr);
		require_action(wolfSSL_UseSNI(httpc->sock.ssl.session_ptr, WOLFSSL_SNI_HOST_NAME, httpc->sock.peer.domain, strlen(httpc->sock.peer.domain)) == WOLFSSL_SUCCESS, exit, err = kGeneralErr);
	}

exit:
	return err;
}

static OSStatus httpc_ssl_handshake(httpc_t * httpc, int timeout_ms)
{
	OSStatus	err = kNoErr;
	int				ssl_ret;
	int				timeout_left_ms = timeout_ms;
	struct sockaddr	sockaddr = {0};
	socklen_t 		addrlen;

	require_action(httpc, exit, err = kParamErr);

	require_action(httpc->sock.ssl.enable, exit, err = kConfigErr);

	require_action(httpc->sock.ssl.session_ptr, exit, err = kGeneralErr);
	
	ssl_ret = wolfSSL_connect(httpc->sock.ssl.session_ptr);
	httpc_log("ssl connect ret: %d", ssl_ret);
    if(ssl_ret != SSL_SUCCESS)
    {
		int ssl_error;

		ssl_error = wolfSSL_get_last_error(httpc->sock.ssl.session_ptr);
		httpc_log("ssl_error: %d", ssl_error);

		switch(ssl_error)
		{
			case WANT_READ:
			case WANT_WRITE:
			{
				do
				{
					if(timeout_left_ms < 20)
					{
						rtos_task_sleep_ms(timeout_left_ms);
						require_action(0, exit, err = kGeneralErr);
					}
					else
					{
						rtos_task_sleep_ms(20);
						timeout_left_ms -= 20;
					}

					if(wolfSSL_is_init_finished(httpc->sock.ssl.session_ptr) == 1)
					{
						httpc->sock.ssl.session_reuse.session_ptr = wolfSSL_get_session(httpc->sock.ssl.session_ptr);

						getpeername(httpc->sock.fd, &sockaddr, &addrlen);

						httpc->sock.ssl.session_reuse.sockaddr = sockaddr;

						break;
					}
				}while(0);
				
				break;
			}
				
			default:
				require_action(0, exit, err = kGeneralErr);
				break;
		}
    }

exit:
	return err;
}

static OSStatus httpc_ssl_shutdown(httpc_t * httpc)
{
	OSStatus	err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	require_action(httpc->sock.ssl.enable, exit, err = kConfigErr);

	require_action(httpc->sock.ssl.session_ptr, exit, err = kGeneralErr);

	require_action(wolfSSL_shutdown(httpc->sock.ssl.session_ptr) == WOLFSSL_SUCCESS, exit, err = kGeneralErr);

exit:
	return err;
}

OSStatus httpc_connect_to_server(httpc_t * httpc)
{
	OSStatus 	err 	 	= kNoErr;
	int				sock_nbio	= 1;
	int				ret			= 0;
	int				sock_error  = 0;
	socklen_t		optlen = 0;
	fd_set 			read_fds, write_fds;
	struct timeval	t;
	struct addrinfo		* res, hints, * curt;
	struct sockaddr_in	* ip4_addr;
	struct sockaddr_in6 * ip6_addr;
	uint64			sys_measure_time_ms = 0;
	uint64			select_wait_remained_time_us = 0;
	httpc_errno_e	httpc_errno = 0;
	enum
	{
		HTTPC_SSL_STA_NONE,
		HTTPC_SSL_STA_CTX_INIT,
		HTTPC_SSL_STA_NEW,
		HTTPC_SSL_STA_SET_FD,
		HTTPC_SSL_STA_SET_SESSION,
		HTTPC_SSL_STA_USE_SNI,
		HTTPC_SSL_STA_HANDSHAKE
	} httpc_ssl_state = HTTPC_SSL_STA_NONE;
	
	rtos_running_time_measure_prepare();

	require_action(httpc, exit, err = kParamErr);

	bzero(&hints, sizeof(struct addrinfo));
	
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	
	if(getaddrinfo_with_pcid(httpc->sock.peer.domain, NULL, &hints, &res, httpc->sock.pdp_cid) != 0)
	{
		httpc_set_errno(httpc, HTTP_ERR_DNS_FAIL);
		require_noerr(err = kGeneralErr, exit);
	}
	else
	{
		for(curt = res; curt != NULL; curt = curt->ai_next)
		{
			ret = socket(curt->ai_family, SOCK_STREAM, 0);
			if(ret < 0)
			{
				httpc_set_errno(httpc, HTTP_ERR_SOCK_CREATE_FAIL);
				require_noerr(err = kGeneralErr, exit);
			}

			httpc->sock.fd = ret;
			httpc->sock.ai_family = curt->ai_family;

			ioctlsocket(httpc->sock.fd, FIONBIO, &sock_nbio);

			httpc_log("local bind arguements:");
			httpc_log("--> pdp cid: %d", httpc->sock.pdp_cid);
			httpc_log("--> socket fd: %d", httpc->sock.fd);
			httpc_log("--> ai family: %d", httpc->sock.ai_family);

			ret = soc_local_bind(httpc->sock.pdp_cid, httpc->sock.fd, curt->ai_family, soc_generate_port());
			if(ret < 0)
			{
				httpc_set_errno(httpc, HTTP_ERR_SOCK_BIND_FAIL);
				require_noerr(err = kGeneralErr, exit);
			}
			
			if(httpc->sock.event_cb)
				httpc->sock.event_cb(httpc, HTTP_EVENT_CONNECTING, 0);
			
			if(curt->ai_family == AF_INET)
			{
				ip4_addr = (struct sockaddr_in *)curt->ai_addr;
				ip4_addr->sin_port = htons(httpc->sock.peer.port);

				httpc_log("IPv4 connect arguements:");
				httpc_log("--> socket fd: %d", httpc->sock.fd);
				httpc_log("--> ai family: %d", ip4_addr->sin_family);
				httpc_log("--> ip addr: 0x%X", ip4_addr->sin_addr.s_addr);
				httpc_log("--> port: %d", httpc->sock.peer.port);

				ret = connect(httpc->sock.fd, ip4_addr, sizeof(struct sockaddr));
			}
			else
			{
				ip6_addr = (struct sockaddr_in6 *)curt->ai_addr;
				ip6_addr->sin6_port = htons(httpc->sock.peer.port);

				ret = connect(httpc->sock.fd, ip6_addr, sizeof(struct sockaddr));
			}

			httpc_log("connect ret: %d, errno: %d", ret, errno);
			
			if(ret == -1 && errno != EINPROGRESS)
			{
				httpc_set_errno(httpc, HTTP_ERR_SOCK_CONN_FAIL);
				require_noerr(err = kGeneralErr, bail1);
			}
			
			t.tv_sec = httpc->sock.connect.timeout_ms / 1000;
			t.tv_usec = httpc->sock.connect.timeout_ms % 1000 * 1000;
			
			FD_ZERO(&read_fds);
			FD_ZERO(&write_fds);
			
			FD_SET(httpc->sock.fd, &read_fds);
			FD_SET(httpc->sock.fd, &write_fds);

		wait:

			rtos_running_time_measure_start();

			httpc_log("conn_timeout_ms: %d", httpc->sock.connect.timeout_ms);
			httpc_log("t.tv_sec: %d", t.tv_sec);
			httpc_log("t.tv_usec: %d", t.tv_usec);

			ret = select(httpc->sock.fd + 1, &read_fds, &write_fds, NULL, &t);

			if(ret <= 0)
			{
				httpc_log("select ret: %d, errno: %d", ret, errno);
				
				httpc_set_errno(httpc, HTTP_ERR_SOCK_CONN_FAIL);
				require_noerr(err = kGeneralErr, bail1);
			}

			if(!FD_ISSET(httpc->sock.fd, &read_fds) && !FD_ISSET(httpc->sock.fd, &write_fds))
			{
				httpc_log("select ret: %d, errno: %d", ret, errno);
				
				httpc_set_errno(httpc, HTTP_ERR_SOCK_CONN_FAIL);
				require_noerr(err = kGeneralErr, bail1);
			}
			else if(FD_ISSET(httpc->sock.fd, &read_fds) && FD_ISSET(httpc->sock.fd, &write_fds))
			{
				optlen = sizeof(sock_error);
				ret = getsockopt(httpc->sock.fd, SOL_SOCKET, SO_ERROR, &sock_error, &optlen);
				if(ret == 0 && sock_error == 0)
				{
					if(httpc->sock.event_cb && httpc->sock.ssl.enable == FALSE)
						httpc->sock.event_cb(httpc, HTTP_EVENT_CONNECTED, 0);

					break;
				}
				else
				{
					httpc_log("getsockopt ret: %d, errno: %d, sock_error: %d", ret, errno, sock_error);
					
					httpc_set_errno(httpc, HTTP_ERR_SOCK_CONN_FAIL);
					require_noerr(err = kGeneralErr, bail1);
				}
			}
			else if(!FD_ISSET(httpc->sock.fd, &read_fds) && FD_ISSET(httpc->sock.fd, &write_fds))
			{
				if(httpc->sock.event_cb && httpc->sock.ssl.enable == FALSE)
					httpc->sock.event_cb(httpc, HTTP_EVENT_CONNECTED, 0);

				break;
			}
			else if(FD_ISSET(httpc->sock.fd, &read_fds) && !FD_ISSET(httpc->sock.fd, &write_fds))
			{
				httpc_log("select ret: %d, errno: %d", ret, errno);
				
				httpc_set_errno(httpc, HTTP_ERR_SOCK_CONN_FAIL);
				require_noerr(err = kGeneralErr, bail1);
			}
			else
			{
				rtos_running_time_measure_stop(sys_measure_time_ms);
				
				if(sys_measure_time_ms * 1000 >= (t.tv_sec * 1000000 + t.tv_usec))
				{
					t.tv_sec = 0;
					t.tv_usec = 0;
				}
				else
				{
					select_wait_remained_time_us = t.tv_sec * 1000000 + t.tv_usec - sys_measure_time_ms * 1000;
					
					t.tv_sec = select_wait_remained_time_us / 1000000;
					t.tv_usec = select_wait_remained_time_us % 1000000;
				}
				
				if(t.tv_sec || t.tv_usec)
				{
					goto wait;
				}
				else
				{
					httpc_set_errno(httpc, HTTP_ERR_SOCK_CONN_FAIL);
					require_noerr(err = kGeneralErr, bail1);
				}
			}
			
		bail1:

			if(err != kNoErr)
			{
				if(httpc->sock.fd >= 0)
				{
					struct linger linger = {0};
					sock_nbio = 0;

					ioctlsocket(httpc->sock.fd, FIONBIO, &sock_nbio);
					
					linger.l_onoff = 1;
					linger.l_linger = httpc->sock.close.linger_time_s;
					
					setsockopt(httpc->sock.fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));

					close(httpc->sock.fd);

					httpc->sock.fd = -1;
				}
			}
			if(httpc->sock.peer.try_multi_host == FALSE)
			{
				break;
			}
		}
		
		freeaddrinfo(res);

		if(err == kNoErr && httpc->sock.ssl.enable)
		{
			OSStatus ssl_err = kNoErr;

			httpc_ssl_state = HTTPC_SSL_STA_CTX_INIT;
			ssl_err = httpc_ssl_ctx_init(httpc);
			require_noerr(ssl_err, bail2);

			httpc_ssl_state = HTTPC_SSL_STA_NEW;
			ssl_err = httpc_ssl_new(httpc);
			require_noerr(ssl_err, bail2);

			httpc_ssl_state = HTTPC_SSL_STA_SET_FD;
			ssl_err = httpc_ssl_set_fd(httpc);
			require_noerr(ssl_err, bail2);

			httpc_ssl_state = HTTPC_SSL_STA_SET_SESSION;
			ssl_err = httpc_ssl_set_session(httpc);
			require_noerr(ssl_err, bail2);

			httpc_ssl_state = HTTPC_SSL_STA_USE_SNI;
			ssl_err = httpc_ssl_use_sni(httpc);
			require_noerr(ssl_err, bail2);

			httpc_ssl_state = HTTPC_SSL_STA_HANDSHAKE;
			ssl_err = httpc_ssl_handshake(httpc, httpc->sock.connect.timeout_ms);
			require_noerr(ssl_err, bail2);

			if(httpc->sock.event_cb)
				httpc->sock.event_cb(httpc, HTTP_EVENT_CONNECTED, 0);

		bail2:
			if(ssl_err != kNoErr)
			{
				struct linger linger = {0};
				sock_nbio = 0;

				switch(httpc_ssl_state)
				{
					case HTTPC_SSL_STA_CTX_INIT:
						// do nothing
						break;
						
					case HTTPC_SSL_STA_NEW:
						httpc_ssl_ctx_deinit(httpc);
						break;

					case HTTPC_SSL_STA_SET_FD:
					case HTTPC_SSL_STA_SET_SESSION:
					case HTTPC_SSL_STA_USE_SNI:
						httpc_ssl_free(httpc);
						httpc_ssl_ctx_deinit(httpc);
						break;

					case HTTPC_SSL_STA_HANDSHAKE:
						httpc_ssl_shutdown(httpc);
						httpc_ssl_free(httpc);
						httpc_ssl_ctx_deinit(httpc);
						break;
				}

				ioctlsocket(httpc->sock.fd, FIONBIO, &sock_nbio);
				
				linger.l_onoff = 1;
				linger.l_linger = httpc->sock.close.linger_time_s;
				
				setsockopt(httpc->sock.fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
				
				close(httpc->sock.fd);
				
				httpc->sock.fd = -1;
			}
			require_noerr_action(ssl_err, exit, err = kGeneralErr;httpc_set_errno(httpc, HTTP_ERR_SOCK_CONN_FAIL));
		}
	}
	
exit:

	if(err != kNoErr)
	{
		if(err == kParamErr)
		{
			httpc_set_errno(httpc, HTTP_ERR_INVALID_OBJ);
		}
	}

	httpc_get_errno(httpc, &httpc_errno);
	if(httpc_errno == HTTP_ERR_SOCK_CONN_FAIL)
	{
		if(httpc->sock.event_cb)
			httpc->sock.event_cb(httpc, HTTP_EVENT_DISCONNECTED, 0);
	}
		
	return err;
}

OSStatus httpc_send(httpc_t * httpc, uint8 * send_data, uint32 send_len)
{
	OSStatus 	err = kNoErr;
	bool			is_locked = FALSE;
	uint32			wm_cnt = 0;
	uint32			actually_write_len = 0;

	require_action(httpc, exit, err = kParamErr);

	if(send_data == NULL || send_len == 0)
	{
		err = rtos_semaphore_release(httpc->sock.send.process.sem);
		require_noerr(err, exit);
	}
	else
	{
		err = httpc_request_wm_lock(&httpc->request);
		require_noerr(err, exit);

		is_locked = TRUE;

		err = wm_cnt(&httpc->request.wm_mgr.handle, &wm_cnt);
		require_noerr(err, exit);
		
		err = wm_write(&httpc->request.wm_mgr.handle, send_data, send_len, &actually_write_len, httpc->request.wm_mgr.handle);
		require_noerr(err, exit);

		if(wm_cnt == 0 && actually_write_len != 0)
		{
			err = rtos_semaphore_release(httpc->sock.send.process.sem);
			require_noerr(err, exit);
		}
	}
	
exit:
	if(is_locked)
		httpc_request_wm_unlock(&httpc->request);

	return err;
}

OSStatus httpc_recv_whole(httpc_t * httpc, uint8 * recvBuf, uint32 toRecvLen, uint32 * pActualRecvLen, bool * isTotallyRecvd)
{
	OSStatus 	err = kNoErr;
	
	err = httpc_response_whole_read(&httpc->response, recvBuf, toRecvLen, pActualRecvLen, isTotallyRecvd);
	require_noerr(err, exit);

exit:
	return err;
}

OSStatus httpc_recv_body(httpc_t * httpc, uint8 * recvBuf, uint32 toRecvLen, uint32 * pActualRecvLen, bool * isTotallyRecvd)
{
	OSStatus 	err = kNoErr;
	
	err = httpc_response_body_read(&httpc->response, recvBuf, toRecvLen, pActualRecvLen, isTotallyRecvd);
	require_noerr(err, exit);

exit:
	return err;
}

OSStatus httpc_recv_pause_retrieve_to_buffer(httpc_t * httpc)
{
	OSStatus	err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	httpc->sock.recv.ioctl.recv_retrieve_pause_enable = TRUE;

exit:
	return err;
}

OSStatus httpc_recv_continue_retrieve_to_buffer(httpc_t * httpc)
{
	OSStatus	err = kNoErr;
	bool			pause_status = FALSE;

	require_action(httpc, exit, err = kParamErr);

	httpc_recv_retrieve_is_paused(httpc, &pause_status);

	if(pause_status == TRUE)
	{
		httpc->sock.recv.ioctl.recv_retrieve_pause_enable = FALSE;

		err = rtos_task_resume(httpc->sock.recv.process.task);
		require_noerr(err, exit);
	}
	
exit:
	return err;
}

OSStatus httpc_dwnlk_pause(httpc_t * httpc)
{
	OSStatus	err = kNoErr;

	require_action(httpc, exit, err = kParamErr);

	httpc->sock.recv.ioctl.dwnlk_pause_enable = TRUE;
	
exit:
	return err;
}

OSStatus httpc_dwnlk_continue(httpc_t * httpc)
{
	OSStatus	err = kNoErr;
	bool			pause_status = FALSE;

	require_action(httpc, exit, err = kParamErr);

	httpc_dwnlk_is_paused(httpc, &pause_status);

	if(pause_status == TRUE)
	{
		httpc->sock.recv.ioctl.dwnlk_pause_enable = FALSE;

		err = rtos_task_resume(httpc->sock.recv.preprocess.task);
		require_noerr(err, exit);
	}
	
exit:
	return err;
}

